﻿using Common;
using Linguist.Acoustic;
using Linguist.Dictionary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Util;

namespace Linguist.Flat
{
    /// <summary>
    /// Constructs a loop of all the context-independent phones. This loop is used in the static flat linguist for detecting
    /// out-of-grammar utterances. A 'phoneInsertionProbability' will be added to the score each time a new phone is entered.
    /// To obtain the all-phone search graph loop, simply called the method {@link #getSearchGraph() getSearchGraph}.
    ///
    /// For futher details of this approach cf. 'Modeling Out-of-vocabulary Words for Robust Speech Recognition', Brazzi,
    //// 2000, Proc. ICSLP
    /// </summary>
    public class CIPhoneLoop
    {
        public static IAcousticModel model;
        public static float logPhoneInsertionProbability;
        public static float logOne = LogMath.LOG_ONE;


        /**
        /// Creates the CIPhoneLoop with the given acoustic model and phone insertion probability
         *
        /// @param model                        the acoustic model
        /// @param logPhoneInsertionProbability the insertion probability
         */
        public CIPhoneLoop(IAcousticModel model,
                           float logPhoneInsertionProbability) 
        {
            model = model;
            logPhoneInsertionProbability = logPhoneInsertionProbability;
        }


        /**
        /// Creates a new loop of all the context-independent phones.
         *
        /// @return the phone loop search graph
         */
        public ISearchGraph getSearchGraph() 
        {
            return new PhoneLoopSearchGraph();
        }
        
    }

    class PhoneLoopSearchGraph : ISearchGraph
    {

        
        protected Dictionary<String, ISearchState> existingStates;
        protected SentenceHMMState firstState;


        /** Constructs a phone loop search graph. */
        public PhoneLoopSearchGraph()
        {
            existingStates = new Dictionary<String, ISearchState>();
            firstState = new UnknownWordState();
            SentenceHMMState branchState = new BranchOutState(firstState);
            attachState(firstState, branchState, CIPhoneLoop.logOne, CIPhoneLoop.logOne);

            SentenceHMMState lastState = new LoopBackState(firstState);
            lastState.setFinalState(true);
            attachState(lastState, branchState, CIPhoneLoop.logOne, CIPhoneLoop.logOne);

            for (IEnumerator<IUnit> i = CIPhoneLoop.model.getContextIndependentUnitIterator(); i.MoveNext(); )
            {
                UnitState unitState = new UnitState(i.Current, HMMPosition.UNDEFINED);

                // attach unit state to the branch out state
                attachState(branchState, unitState, CIPhoneLoop.logOne, CIPhoneLoop.logPhoneInsertionProbability);

                IHMM hmm = CIPhoneLoop.model.lookupNearestHMM
                        (unitState.getUnit(), unitState.getPosition(), false);
                IHMMState initialState = hmm.getInitialState();
                HMMStateState hmmTree = new HMMStateState(unitState, initialState);
                addStateToCache(hmmTree);

                // attach first HMM state to the unit state
                attachState(unitState, hmmTree, CIPhoneLoop.logOne, CIPhoneLoop.logOne);

                // expand the HMM tree
                HMMStateState finalState = expandHMMTree(unitState, hmmTree);

                // attach final state of HMM tree to the loopback state
                attachState(finalState, lastState, CIPhoneLoop.logOne, CIPhoneLoop.logOne);
            }
        }


        /**
        /// Retrieves initial search state
         *
        /// @return the set of initial search state
         */
        public ISearchState getInitialState()
        {
            return firstState;
        }


        /**
        /// Returns the number of different state types maintained in the search graph
         *
        /// @return the number of different state types
         */
        public int getNumStateOrder()
        {
            return 5;
        }

        public Boolean getWordTokenFirst()
        {
            return false;
        }

        /**
        /// Checks to see if a state that matches the given state already exists
         *
        /// @param state the state to check
        /// @return true if a state with an identical signature already exists.
         */
        private SentenceHMMState getExistingState(SentenceHMMState state)
        {
            return (SentenceHMMState)existingStates[state.getSignature()];
        }


        /**
        /// Adds the given state to the cache of states
         *
        /// @param state the state to add
         */
        protected void addStateToCache(SentenceHMMState state)
        {
            existingStates.Add(state.getSignature(), state);
        }


        /**
        /// Expands the given hmm state tree
         *
        /// @param parent the parent of the tree
        /// @param tree   the tree to expand
        /// @return the final state in the tree
         */
        protected HMMStateState expandHMMTree(UnitState parent, HMMStateState tree)
        {
            HMMStateState retState = tree;
            foreach (HMMStateArc arc in tree.getHMMState().getSuccessors())
            {
                HMMStateState newState;
                if (arc.getHMMState().isEmitting())
                {
                    newState = new HMMStateState(parent, arc.getHMMState());
                }
                else
                {
                    newState = new NonEmittingHMMState(parent, arc.getHMMState());
                }
                SentenceHMMState existingState = getExistingState(newState);
                float logProb = arc.getLogProbability();
                if (existingState != null)
                {
                    attachState(tree, existingState, CIPhoneLoop.logOne, logProb);
                }
                else
                {
                    attachState(tree, newState, CIPhoneLoop.logOne, logProb);
                    addStateToCache(newState);
                    retState = expandHMMTree(parent, newState);
                }
            }
            return retState;
        }


        protected void attachState(SentenceHMMState prevState,
                                   SentenceHMMState nextState,
                                   float logLanguageProbability,
                                   float logInsertionProbability)
        {
            SentenceHMMStateArc arc = new SentenceHMMStateArc
                    (nextState,
                     logLanguageProbability,
                     logInsertionProbability);
            prevState.connect(arc);
        }
    }

    class UnknownWordState: SentenceHMMState,IWordSearchState 
    {

        public IPronunciation getPronunciation() 
        {
            return Word.UNKNOWN.getPronunciations()[0];
        }


        override public int getOrder() {
            return 0;
        }


        public String getName() {
            return "UnknownWordState";
        }


        /**
        /// Returns true if this UnknownWordState indicates the start of a word. Returns false if this UnknownWordState
        /// indicates the end of a word.
         *
        /// @return true if this UnknownWordState indicates the start of a word, false if this UnknownWordState indicates the
        ///         end of a word
         */
        public Boolean isWordStart() {
            return true;
        }
    }

    class LoopBackState:SentenceHMMState 
    {
        public LoopBackState(SentenceHMMState parent) 
            :base("CIPhonesLoopBackState", parent, 0)
        {
            
        }

        override public int getOrder() {
            return 1;
        }
    }

    class BranchOutState: SentenceHMMState 
    {
        public BranchOutState(SentenceHMMState parent) 
            :base("BranchOutState", parent, 0)
        {
            
        }

        override public int getOrder() {
            return 1;
        }

    }
}
